package edu.kit.csl.pisa.training.eva2;

/*
This file is part of the PISA Alignment Tool.

Copyright (C) 2013
Karlsruhe Institute of Technology
Cognitive Systems Lab (CSL)
Felix Stahlberg

PISA is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

PISA is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PISA. If not, see <http://www.gnu.org/licenses/>.
*/

import edu.kit.csl.pisa.datatypes.Alignment;
import edu.kit.csl.pisa.datatypes.Sentence;
import edu.kit.csl.pisa.datatypes.SentenceAlignment;
import edu.kit.csl.pisa.datatypes.SentencePair;
import edu.kit.csl.pisa.io.Logger;
import edu.kit.csl.pisa.models.AlignmentModel;
import edu.kit.csl.pisa.ui.Configuration;
import eva2.server.go.individuals.AbstractEAIndividual;
import eva2.server.go.populations.Population;
import eva2.server.go.problems.AbstractOptimizationProblem;
import eva2.server.go.strategies.InterfaceOptimizer;
import eva2.tools.math.RNG;

/**
 * This class represents an alignment problem for a single sentence pair.
 */
public class AlignmentProblem extends AbstractOptimizationProblem 
		implements java.io.Serializable {
	
	private static final long serialVersionUID = 2775228439291378641L;
	
	private SentencePair pair;
	private AlignmentModel model;
	private int popSize;

	/**
	 * This constructor creates an instance and set the associated alignment
	 * model and sentence pair to zero.
	 */
	public AlignmentProblem() {
		initTemplate();
	}
	
	/**
	 * This constructor should be used to create a feasible problem instance.
	 * 
	 * @param model the target alignment model used for evaluation
	 * @param pair the sentence pair for which the alignment is to be optimized
	 * @param popSize size of the population
	 */
    public AlignmentProblem(AlignmentModel model, SentencePair pair,
    		int popSize) {
    	this.pair = pair;
    	this.model = model;
    	this.popSize = popSize;
    	initTemplate();
    }
    
    /**
     * This is the copy constructor. The sentence pair and alignment model are
     * just copied by reference, the individual template is copied by value.
     * 
     * @param b the problem to copy
     */
    public AlignmentProblem(AlignmentProblem b) {
    	this.pair = b.pair;
    	this.model = b.model;
    	if (b.m_Template != null) {
    		m_Template = (AbstractEAIndividual) b.m_Template.clone();
    	}
    }

    /** 
     * This method returns a deep clone of the problem. This functionality is
     * equal to the copy constructor
     * 
     * @return  the clone
     * @see #AlignmentProblem(AlignmentProblem)
     */
    public Object clone() {
        return (Object) new AlignmentProblem(this);
    }
    
    /**
     * Initializes the problem. The main thing to do here is to initialize
     * the individual template to a dummy sentence alignment individual.
     */
	@Override
	public void initProblem() {
		if (m_Template == null) {
			this.m_Template = new IndividualSentenceAlignment
					(pair.getViterbiAlignment());
		}
	}
	
	/* (non-Javadoc)
	 * Initializes the template individual.
	 */
	private void initTemplate() {
		if (m_Template == null) {
			this.m_Template = new IndividualSentenceAlignment
					(pair.getViterbiAlignment());
		}
	}
	
	/**
	 * Initializes the population. The initial population consists of
	 * some representative individuals for current alignments for the given
	 * sentence pair and some random alignments. The distribution among these
	 * two types is defined by the <code>MaxAlgorithmLegacyFraction</code>
	 * parameter.
	 * 
	 * @see #setSentencePair(SentencePair)
	 * @see eva2.server.go.problems.AbstractOptimizationProblem
	 * 			#initPopulation(eva2.server.go.populations.Population)
	 */
	@Override
	public void initPopulation(Population population) {
        population.clear();
        population.setTargetSize(popSize);
        Alignment tmpl = null;
        for (SentenceAlignment sa : pair) {
            population.add(generateIndy(sa));
            tmpl = sa.getAlignment();
        }
        // Fill up with random alignments
        final Sentence srcSen = pair.getSourceSentence();
        final Sentence trgtSen = pair.getTargetSentence();
        int minTrgtWords = 1 + trgtSen.getLength() /
        		Configuration.getSingleton().getInteger("maxWordLength");
        int maxTrgtWords = Math.min(
        		srcSen.getLength() * Configuration.getSingleton()
        			.getInteger("maxFertility"),
        		(int) (trgtSen.getLength() * 0.8));
        if (tmpl == null) {
        	tmpl = new Alignment(srcSen.getLength(),
        			(short) trgtSen.getLength(), minTrgtWords);
        }
        if (minTrgtWords >= maxTrgtWords) {
        	Logger.getSingleton().debug("Could not create random alignments "
        		+ "for the sentence pair\n" + pair);
        	for (int i = population.size(); i < population.getTargetSize(); i++) {
        		population.add(generateIndy(new SentenceAlignment(
        			new Alignment(tmpl), srcSen, trgtSen, 0.0)));
        	}
        	population.init();
        	return;
        }
        for (int i = population.size(); i < population.getTargetSize(); i++) {
        	int safetyCounter = 0;
        	Alignment randAlign = null;
        	do {
        		randAlign = Alignment.generateRandomAlignment(
        			srcSen.getLength(), RNG.randomInt(minTrgtWords,
        					maxTrgtWords), trgtSen.getLength());
        	} while (!randAlign.forceLocksFrom(tmpl) && safetyCounter++ < 10);
        	population.add(generateIndy(new SentenceAlignment(randAlign,
        		srcSen, trgtSen, 0.0)));
        }
        population.init();
	}
	
	/*
	 * Helper method for init population.
	 */
	private IndividualSentenceAlignment generateIndy(SentenceAlignment a) {
		IndividualSentenceAlignment indy = (IndividualSentenceAlignment)
        		((AbstractEAIndividual) m_Template).clone();
		indy.setGenotype(a);
        indy.init(this);
        return indy;
	}
    
	/**
	 * Evaluate an individual. Calculates P(a,f|e) for the corresponding
	 * {@link SentenceAlignment} and uses this value as new fitness score
	 * of the individual
	 * 
	 * @param individual the individual to evaluate
	 * @throws CastException if individual has no {@link SentenceAlignment}. 
	 */
	@Override
	public void evaluate(AbstractEAIndividual individual) {
		individual.SetFitness(0, -model.calculateAlignmentProbability(
				((InterfaceDataTypeSentenceAlignment) individual)
					.getSentenceAlignment()));
	}

	/**
	 * This method returns a string describing the optimization problem.
	 * 
     * @param opt       The Optimizer that is used or had been used.
     * @return The description.
     */
	@Override
	public String getStringRepresentationForProblem(InterfaceOptimizer opt) {
		StringBuffer sb = new StringBuffer(200);
        sb.append("An alignment problem:\n");
        sb.append(globalInfo());
        sb.append("Model   : "); 
        sb.append(this.model.getName());
        sb.append("Sentence Pair   : "); 
        sb.append(pair == null ? "null" : pair.toString());
        return sb.toString();
	}
	
    /** 
     * This method allows you to output a string that describes a found solution
     * in a way that is most suitable for a given problem.
     * 
     * @param individual    The individual that is to be shown.
     * @return The description.
     * @see edu.kit.csl.pisa.training.eva2.IndividualSentenceAlignment#toString()
     */
	@Override
    public String getSolutionRepresentationFor(AbstractEAIndividual individual) {
        this.evaluate(individual);
        String result = "Sentence alignment problem:\n";
        result += individual.getStringRepresentation() + "\n";
        result += "Scores " + individual.getFitness(0) + " alignment score!";
        return result;
    }

/******************************************************************************
 * These are for GUI
 */
	
    /** 
     * This method allows the CommonJavaObjectEditorPanel to read the
     * name to the current object.
     * 
     * @return the name.
     */
	@Override
    public String getName() {
        return "Maximize alignment score respect to the current model.";
    }

    /** 
     * This method returns a global info string
     * 
     * @return a description
     */
    public static String globalInfo() {
        return "The task in this problem is to maximize the alignment score, "
        	+ "given the source and target sentences and the alignment model.";
    }
}
